package cn.xuguowen.util;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.Collections2;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.yaml.snakeyaml.events.Event;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

/**
 * ClassName: CacheUtil
 * Package: cn.xuguowen.util
 * Description: 基于guava的本地缓存+函数式编程实现二级缓存
 *
 * @Author 徐国文
 * @Create 2023/5/13 19:05
 * @Version 1.0
 */
@Component
@Slf4j
public class GuavaCacheUtil<K, V> {

    // 本地缓存开关
    @Value("${guava.cache.switch}")
    private Boolean switchCache;

    // guava的本地缓存！其中这里使用了构建者模式
    // guavaCache本地缓存失效方案expireAfterWrites和refreshAfterWrites.他俩的区别需要注意一下
    private Cache<String, String> LOCALCACHE = CacheBuilder
            .newBuilder()
            .maximumSize(5000)
            // .refreshAfterWrite(3, TimeUnit.SECONDS)
            .expireAfterWrite(3, TimeUnit.SECONDS)
            .build();

    /**
     * 从本地缓存中获取结果
     *
     * @param skuIds      缓存key
     * @param cachePrefix 缓存前缀
     * @param clazz       将字符串的内容反序列号为对象
     * @param function    函数式接口
     * @return
     */
    public Map<K, V> getResult(List<K> skuIds, String cachePrefix, Class<V> clazz, Function<List<K>, Map<K, V>> function) {
        // 声明空的map返回结果
        Map<K, V> returnMap = new HashMap<>(16);

        // 1.校验参数
        if (CollUtil.isEmpty(skuIds)) {
            return returnMap;
        }

        // 2.检查本地缓存开关是否开启
        if (!switchCache) {
            // 没有开启
            returnMap = function.apply(skuIds);
            return returnMap;
        }

        // 本地缓存中不存在的数据
        List<K> noCacheIdList = new ArrayList<>();

        // 3.遍历skuIds集合，从本地缓存中获取数据
        for (K skuId : skuIds) {
            // 组装缓存key
            String cacheKey = cachePrefix + "_" + skuId;
            // 从本地缓存中根据key获取数据
            String content = LOCALCACHE.getIfPresent(cacheKey);
            // 判断是否从本地缓存中获取到了对应的数据
            if (StrUtil.isNotBlank(content)) {
                // 将字符串数据反序列化为对应的对象
                V v = JSON.parseObject(content, clazz);
                // 将信息存储到返回结果中
                returnMap.put(skuId, v);
            } else {
                // 说明当前skuId没有进行本地缓存
                noCacheIdList.add(skuId);
            }
        }

        // 4.判断noCacheIdList集合中是否有内容。如果没有内容，说明上面的else就没进去过，所以可以直接返回returnMap
        if (CollUtil.isEmpty(noCacheIdList)) {
            return returnMap;
        }

        // 5.查询RPC接口获取数据
        Map<K, V> noCacheReturnMap = function.apply(noCacheIdList);
        if (CollUtil.isEmpty(noCacheReturnMap)) {
            return returnMap;
        }

        // 6.将数据放到本地缓存中,并且也放到返回结果中
        Set<Map.Entry<K, V>> entries = noCacheReturnMap.entrySet();
        for (Map.Entry<K, V> entry : entries) {
            K key = entry.getKey();
            V value = entry.getValue();

            returnMap.put(key, value);

            String cacheKey = cachePrefix + "_" + key;
            LOCALCACHE.put(cacheKey, JSON.toJSONString(value));
        }

        return returnMap;
    }
}
